package name.shareview;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;

import javax.mail.BodyPart;
import javax.mail.Folder;
import javax.mail.FolderNotFoundException;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.NoSuchProviderException;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.Store;
import android.os.Handler;
import android.util.Log;

public class EmailChecker implements Runnable {

	public static String SV = "_sv_";
	public static final String SUBJECT_ARG_DELIMETER = ",";
	//interval to wait between checking the email (in miliseconds - 1 sec == 1000 miliseconds)
	private final long delay = 2000;  
	private Handler mHandler;
	
	//Globals
	private Thread getter = null;
	boolean running = false;
	
	public static Date mostRecent = null;
	
	Store store = null;
  Folder inboxfolder = null;
    
  Message[] msgs = null;
  // The index in the arguments of the email subject line that contains the 'main' keyword
  //  (such as "UPDATE") (0 based)
  //  Sample subject: "_sv_,JOIN,Jason,Session:2011-12-05"
  protected final int MAIN_SUBJECT_KEYWORD_INDEX = 1;
  protected final int FROM_KEYWORD_INDEX = 2;
  protected final int SESSION_ID_KEYWORD_INDEX   = 3;
	
  // Constructor that takes a Handler that can receive messages from this BG thread
	public EmailChecker(Handler h)
	{
		mHandler = h;
	}
	
	// Default constructor (useful for subclasses)
	public EmailChecker()
	{
		
	}
    
  @Override
	public void run() {
		
		Log.i("ShareView", " -- EmailChecker Thread STARTED -- ");
    	
		Log.i("EMAIL", "Connecting...");
    connect();
		
		while(running)
		{
    	//Grab the email msgs that are newer than the last time we checked
			List<Message> msgList = getEmail();
			
			//Controls whether the UI gets redrawn
			boolean passMsg = false;
			
			try 
			{		
				//Process each message pulled down
				for(Message m : msgList)
				{
					//Process messages in here
					Log.i("EMAIL", m.getSubject());
					
					//Parse the subject
					String[] args = parseArgs(m.getSubject());
					
					//If this is one of out own messages, then skip it
					if(args[2].equals(ShareView.myName))
						continue;
					
					//Check for attachment
					if(args[1].equals("UPDATE"))
					{
						Log.i("EMAIL", "Arg was \"UPDATE\". Now I'll check the map for the person's name.");
						String name = args[2];
						handleUpdate(name, m);
										//						// Only deal with update messages for participants we actually know about
										//						if(ShareView.map.containsKey(args[2])) {
										//							String fileName = getPic(m);
										//							int index = ShareView.map.get(args[2]);
										//							Log.i("EMAIL", "Arg was \"UPDATE\". Index from the map was: " + index);
										//
										//							// Make sure we got a valid index back from the map
										//							//  (during dev, etc, we might be reading an update before we've added that person to the map)
										//							if(index >= 0 && index < ShareView.MAX_NUM_PARTICIPANTS) {
										//								ShareView.fileNames[index] = fileName;
										//							}
										//							passMsg = true;
										//						}
						if(ShareView.map.containsKey(args[2])) {
							passMsg = true;
						}
						
					}
					else if(args[1].equals("JOIN"))
					{
						//passMsg = addPeer(args[2]);
						
						// We may have to process multiple JOINS this round, and if one of the joins
						//  would try to add more participants than our max allowed, addPeer() would return
						//  false.  So we want to preserve a single return value of "true" from any call
						//  to addPeer() for this round of email msgs. So we "or" passMsg with the latest
						//  return of addPeer().
						passMsg = passMsg || addPeer(args[2]);
					}
					else if(args[1].equals("END:Person"))
					{
						passMsg = removePeer(args[2]);
					}
				}
			} // end processing all new messages pulled down.
			catch (MessagingException e) 
			{
				e.printStackTrace();
			}
			
			Log.i("EMAIL", "Near end of run(), boolean \"passMsg\": " + passMsg);
			//Passing msg to UI thread
			if(passMsg)
			{
				android.os.Message msg = mHandler.obtainMessage();
				// As of 12/4/11, the handler (in ShareView.java) does not actually use this "msg",
				//  but rather issues a "redraw()" upon this notification. The redraw pulls names and
				//  image filenames from ShareView's "map" and "fileNames" data structures.
				//  The method of the handler obj that gets called is "handleMessage()".
				Log.i("EMAIL", "At end of run(), about to call mHandler.sendMessage(), since passMsg was true.");
				mHandler.sendMessage(msg);
			}
            
            
			try { Thread.sleep(delay); } catch (InterruptedException e) { ; }
		} // end while(running)
		
		Log.i("EMAIL", "Closing connection...");
		close();
	} /////////////////// end run() ////////////////////
	
	/**
	 * Open a connection with the email server
	 */
  public void connect()
	{
		try 
	  {
			Properties props =  System.getProperties();
		  Session session  = Session.getInstance(props,null);
		  props.setProperty("mail.store.protocol", "imaps");
			store = session.getStore("imaps");
			store.connect("imap.gmail.com","shareview972","somepassword");
			Folder folder = store.getDefaultFolder();
			inboxfolder = folder.getFolder("INBOX");
			inboxfolder.open(Folder.READ_ONLY);
		} 
	  catch (NoSuchProviderException e) 
	  {
			Log.i("EMAIL", e.toString());	
		} 
	  catch (MessagingException e) 
		{
	    	Log.i("EMAIL", e.toString());
		}
	} /////////// end connect() ////////////////////
	
    
  /**
	 * Close the connection with the email server
	 */
	public void close()
	{
	    try 
	    {
	    	inboxfolder.close(false);
				store.close();
	    } 
	    catch (MessagingException e) 
	    {
	    	Log.i("EMAIL", e.toString());
		}
	}
	
	/**
	 * Worker fn for pulling the email.  
	 * Obtains a list of messages that are NEWER THAN THE MOST RECENT ONE that we obtained last 
	 * time, then sets the date/time of the most recent msg from this new list.
	 * @return  The list of new msgs.
	 */
	public List<Message> getEmail()
	{
		List<Message> msgList = new ArrayList<Message>();
		
		try 
		{
			msgs = inboxfolder.getMessages(); // get all message objects from the inbox folder
		
			// Find any msg that: (1) is newer than the most-recent one from last time and 
			//                    (2) has a subject that starts with our special str (i.e., "_sv_") 
			for( Message m : msgs)
			{
				if((mostRecent == null || mostRecent.before(m.getSentDate())) && m.getSubject().startsWith(SV))
					msgList.add(m);
			}
			// Reset the date/time of the most recent email that we just checked
			if(!msgList.isEmpty()) {
				mostRecent = msgList.get(msgList.size()-1).getSentDate();
				// TODO: also store a ptr to the most recent email msg, if I have to add s ShareView
				//  static member for other purposes?
				
			}
		} 
		catch (MessagingException e) 
		{
			Log.i("EMAIL", e.toString());
		}
		
		return msgList;
	} //////////////////// end getEmail() ////////////////
	
	//Start thread
	public void start()
	{
		/*"A class that implements Runnable can run without subclassing Thread by instantiating a 
		 * Thread instance and passing itself in as the target. ...the Runnable interface should be 
		 * used if you are only planning to override the run() method and no other Thread methods."
		 */
		getter = new Thread(this); // this object is the target of the thread
		running = true;
    getter.start(); // in effect, this calls the run() method of this class
	}
	
	//Resume (or start) the thread
	public void resume()
	{
		running = true;
		if(getter == null) {
			start();
		}
	}
	
	//Pause the thread
	public void pause()
	{
		running = false;
		Log.i("ShareView", " -- EmailChecker Thread PAUSED -- ");
	}
	
	/*
	 * Protocol methods
	 */
	
	/**
	 * Parse subject line into tokens, splitting the line by a delimiter.
	 */
	public String[] parseArgs(String s)
	{
		return s.split(SUBJECT_ARG_DELIMETER);
	}
	
	//Pull down the attachment, write it to the sdcard, and return a filename
	public String getPic(Message m)
	{
		String fileName = null;
		
		try {
			Multipart mul = (Multipart)m.getContent();
			
			for (int i = 0; i < mul.getCount(); i++) {
		        BodyPart bodyPart = mul.getBodyPart(i);
		        if(Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition()))
		        {
		        	Log.i("EMAIL", "Attachment present");
		        	fileName = bodyPart.getFileName();
		        	InputStream is = bodyPart.getInputStream();
		        	// If the 'filename' is not a fullpath, make it one by prepending the correct path
		        	if( !fileName.startsWith(ShareView.shareViewStorageDirFullPath)) {
		        		fileName = ShareView.shareViewStorageDirFullPath + File.separatorChar + fileName;
		        	}
		          File f = new File(fileName);
		            
		          FileOutputStream fos = new FileOutputStream(f);
		          byte[] buf = new byte[4096];
		          int bytesRead;
		          while((bytesRead = is.read(buf))!=-1) {
		              fos.write(buf, 0, bytesRead);
		          }
		          fos.close();
		          Log.i("EMAIL", "Incoming attachment file was written to disk.");
		            
		        }
			}

		} catch (IOException e) {
			Log.i("EMAIL", e.toString());
		} catch (MessagingException e) {
			Log.i("EMAIL", e.toString());
		}
		
		return fileName;
	}
	
	/**
	 * addPeer()
	 */
	public boolean addPeer(String name)
	{
		//int i = 2;
		//String tempName = name;
		boolean change = false;
		
		//Check that we don't already have 4 peers
		if(ShareView.map.size() < ShareView.MAX_NUM_PARTICIPANTS )
		{
			//Check for duplicate names
//			while(ShareView.map.containsKey(tempName))
//			{
//				tempName = name + " " + Integer.toString(i);
//				i++;
//			}
			
			//Look for the first available position in the map and add the peer
			for(int j = 0; j < ShareView.MAX_NUM_PARTICIPANTS; j++)
			{
				if(!ShareView.map.containsValue(j))
				{
					ShareView.map.put(name, j);
					change = true;
				}
			}
		}
		Log.i("EMAIL", "At end of addPeer(), \"change\" boolean: " + change + ". Map is: " + ShareView.map.toString());
		return change;
	}
	
	/**
	 * removePeer()
	 */
	public boolean removePeer(String name)
	{
		boolean change = false;
		
		if(ShareView.map.containsKey(name))
		{
			//Get the index from the map
			int index = ShareView.map.get(name);
			//Update the filename array
			ShareView.fileNames[index] = "";
			//Finally remove the peer from the map
			ShareView.map.remove(name);
			change = true;
		}
		
		return change;
	}
	
	/**
	 * getLatestMsg() : Makes a networking call to obtain the newest message in the inbox
	 *                   (or null if it was unable to make the networking call).
	 *                  This method should NOT be called from a UI thread since it makes a 
	 *                  networking call. If you did, it would crash in Android 4.0 or newer.  
	 * 
	 * @return Message
	 * @throws MessagingException
	 * @throws FolderNotFoundException
	 * Original Author: Jason Frank
	 */
	protected Message getLatestMsg() throws MessagingException, FolderNotFoundException
	{
		// Get an open connection to the email 'inboxfolder'
		connect();
		
		try {
			// (getMessageCount() will provide the num of the latest msg)
			Message latestMsg = inboxfolder.getMessage(inboxfolder.getMessageCount());
			// Close the connection to the 'inboxfolder'
			//  CAN'T CLOSE IT HERE - the caller in the UI thread needs to inspect the msg
			//   and I guess they can't if the folder is closed.
			// close();
			return  latestMsg;
		} 
		catch (MessagingException e) {
			e.printStackTrace();
			Log.e("EMAIL", e.toString());
			// Close the connection to the 'inboxfolder'
			//close();
		}
		return null;
	} /////////////////// end getLatestMsg() ///////////////
 	
	/**
	 * Takes a person's name and an email msg and updates the ShareView peer map
	 *  with the msg's attached pic filename.
	 * @param name
	 * @param msg
	 */
	protected void handleUpdate(String name, Message msg) {
		// Only deal with update messages for participants we actually know about
		if(ShareView.map.containsKey(name)) {
			String fileName = getPic(msg);
			int index = ShareView.map.get(name);
			Log.i("EMAIL", "Arg was \"UPDATE\". Index from the map was: " + index);
	
			// Make sure we got a valid index back from the map
			//  (during dev, etc, we might be reading an update before we've added that person to the map)
			if(index >= 0 && index < ShareView.MAX_NUM_PARTICIPANTS) {
				ShareView.fileNames[index] = fileName;
			}
		}
	} ////////////// end handleUpdate() //////////////////////

	public String getMainSubjectKeyword(String subject) {
		
		String[] args = parseArgs(subject);
		
		return args[MAIN_SUBJECT_KEYWORD_INDEX];
	}
	
	public String getSessionIdStrFromSubject(String subject) {
		String[] args = parseArgs(subject);
		return args[SESSION_ID_KEYWORD_INDEX];
	}
	
	public String getFromPerson(String subject) {
		String[] args = parseArgs(subject);
		return args[FROM_KEYWORD_INDEX];
	}
	
}
